﻿//---------------------------------------------------------------------------
//
// Copyright (C) Microsoft Corporation.  All rights reserved.
//
// File: TokenizerHelper.cs
//
// Description: This file contains the implementation of TokenizerHelper.
//              This class should be used by most - if not all - MIL parsers.
//
// History:
//  05/19/2003 : Microsoft - Created it
//  05/20/2003 : Microsoft - Moved to Shared
//
//---------------------------------------------------------------------------

using System;
using System.Globalization;

namespace HandyControl.Tools;

internal class TokenizerHelper
{
    private char _quoteChar;

    private char _argSeparator;

    private string _str;

    private int _strLen;

    private int _charIndex;

    private int _currentTokenIndex;

    private int _currentTokenLength;

    public bool FoundSeparator { get; private set; }

    public TokenizerHelper(string str, IFormatProvider formatProvider)
    {
        var numberSeparator = GetNumericListSeparator(formatProvider);
        Initialize(str, '\'', numberSeparator);
    }

    private void Initialize(string str, char quoteChar, char separator)
    {
        _str = str;
        _strLen = str?.Length ?? 0;
        _currentTokenIndex = -1;
        _quoteChar = quoteChar;
        _argSeparator = separator;

        // immediately forward past any whitespace so
        // NextToken() logic always starts on the first
        // character of the next token.
        while (_charIndex < _strLen)
        {
            if (!char.IsWhiteSpace(_str, _charIndex))
            {
                break;
            }

            ++_charIndex;
        }
    }

    public string GetCurrentToken() =>
        _currentTokenIndex < 0 ? null : _str.Substring(_currentTokenIndex, _currentTokenLength);

    internal bool NextToken() => NextToken(false);

    public bool NextToken(bool allowQuotedToken) => NextToken(allowQuotedToken, _argSeparator);

    public bool NextToken(bool allowQuotedToken, char separator)
    {
        _currentTokenIndex = -1; // reset the currentTokenIndex
        FoundSeparator = false; // reset

        // If we're at end of the string, just return false.
        if (_charIndex >= _strLen)
        {
            return false;
        }

        var currentChar = _str[_charIndex];

        // setup the quoteCount
        var quoteCount = 0;

        // If we are allowing a quoted token and this token begins with a quote,
        // set up the quote count and skip the initial quote
        if (allowQuotedToken &&
            currentChar == _quoteChar)
        {
            quoteCount++; // increment quote count
            ++_charIndex; // move to next character
        }

        var newTokenIndex = _charIndex;
        var newTokenLength = 0;

        // loop until hit end of string or hit a , or whitespace
        // if at end of string ust return false.
        while (_charIndex < _strLen)
        {
            currentChar = _str[_charIndex];

            // if have a QuoteCount and this is a quote
            // decrement the quoteCount
            if (quoteCount > 0)
            {
                // if anything but a quoteChar we move on
                if (currentChar == _quoteChar)
                {
                    --quoteCount;

                    // if at zero which it always should for now
                    // break out of the loop
                    if (0 == quoteCount)
                    {
                        ++_charIndex; // move past the quote
                        break;
                    }
                }
            }
            else if (char.IsWhiteSpace(currentChar) || currentChar == separator)
            {
                if (currentChar == separator)
                {
                    FoundSeparator = true;
                }
                break;
            }

            ++_charIndex;
            ++newTokenLength;
        }

        // if quoteCount isn't zero we hit the end of the string
        // before the ending quote
        if (quoteCount > 0)
        {
            throw new InvalidOperationException("TokenizerHelperMissingEndQuote");
        }

        ScanToNextToken(separator); // move so at the start of the nextToken for next call

        // finally made it, update the _currentToken values
        _currentTokenIndex = newTokenIndex;
        _currentTokenLength = newTokenLength;

        if (_currentTokenLength < 1)
        {
            throw new InvalidOperationException("TokenizerHelperEmptyToken");
        }

        return true;
    }

    private void ScanToNextToken(char separator)
    {
        // if already at end of the string don't bother
        if (_charIndex >= _strLen) return;

        var currentChar = _str[_charIndex];

        // check that the currentChar is a space or the separator.  If not
        // we have an error. this can happen in the quote case
        // that the char after the quotes string isn't a char.
        if (currentChar != separator && !char.IsWhiteSpace(currentChar))
        {
            throw new InvalidOperationException("TokenizerHelperExtraDataEncountered");
        }

        // loop until hit a character that isn't
        // an argument separator or whitespace.
        // !!!Todo: if more than one argSet throw an exception
        var argSepCount = 0;
        while (_charIndex < _strLen)
        {
            currentChar = _str[_charIndex];

            if (currentChar == separator)
            {
                FoundSeparator = true;
                ++argSepCount;
                _charIndex++;

                if (argSepCount > 1)
                {
                    throw new InvalidOperationException("TokenizerHelperEmptyToken");
                }
            }
            else if (char.IsWhiteSpace(currentChar))
            {
                ++_charIndex;
            }
            else
            {
                break;
            }
        }

        // if there was a separatorChar then we shouldn't be
        // at the end of string or means there was a separator
        // but there isn't an arg

        if (argSepCount > 0 && _charIndex >= _strLen)
        {
            throw new InvalidOperationException("TokenizerHelperEmptyToken");
        }
    }

    internal static char GetNumericListSeparator(IFormatProvider provider)
    {
        var numericSeparator = ',';

        // Get the NumberFormatInfo out of the provider, if possible
        // If the IFormatProvider doesn't not contain a NumberFormatInfo, then
        // this method returns the current culture's NumberFormatInfo.
        var numberFormat = NumberFormatInfo.GetInstance(provider);

        // Is the decimal separator is the same as the list separator?
        // If so, we use the ";".
        if (numberFormat.NumberDecimalSeparator.Length > 0 && numericSeparator == numberFormat.NumberDecimalSeparator[0])
        {
            numericSeparator = ';';
        }

        return numericSeparator;
    }
}
